﻿using PBO.Network.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PBO.Network.Proxy
{
    internal class BattleAgent
    {
        #region Varibles

        private BattleAgentServer _server;
        private BattleMode _battleMode;
        private List<BattleRule> _rules;
        private int _randomSeed;
        private BattleInfo _battleInfo;

        private Dictionary<int, UserInfo> _users = new Dictionary<int, UserInfo>();
        private List<int> _observers = new List<int>();
        private int observeHost;

        private int _waitUserCount;
        private object _locker = new object();

        private int _agreeTieCounter;
        private bool _tieing;

        #endregion

        public BattleAgent(BattleMode mode, List<BattleRule> rules, BattleAgentServer server)
        {
            _server = server;
            _battleMode = mode;
            _rules = rules;
            _randomSeed = new Random().Next();
            _waitUserCount = mode == BattleMode.Double_4P ? 4 : 2;
        }

        public void UserLogon(int identity, string name)
        {
            if (!_users.ContainsKey(identity))
            {
                lock (_locker)
                {
                    if (_waitUserCount == 0) return;
                }
                UserInfo info = new UserInfo() { identity = name };
                _users[identity] = info;
            }
        }

        public void ReceiveTeam(int identity, byte position, string name, List<byte> teamData)
        {
            if (_users.ContainsKey(identity))
            {
                _users[identity].identity = name;
                _users[identity].team = teamData;
                _users[identity].position = position;
                lock (_locker)
                {
                    Interlocked.Decrement(ref _waitUserCount);
                    if (_waitUserCount == 0)
                    {
                        SendData();
                    }
                }
            }
        }

        private void SendData()
        {
            var keys = _users.Keys.ToArray();

            for (int i = 0; i < keys.Length; i++)
            {
                for (int j = i + 1; j < keys.Length; j++)
                {
                    int k1 = keys[i];
                    int k2 = keys[j];
                    _server.SendTeam(k1, _users[k2].position, _users[k2].identity, _users[k2].team);
                }
            }

            Parallel.ForEach(keys, k => _server.SendBattleData(k, _rules, _randomSeed));
            observeHost = keys[0];
            _server.RegistObserver(observeHost);
        }

        public void ReceiveMove(int identity, PlayerMove move)
        {
            if (_users.ContainsKey(identity))
            {
                Parallel.ForEach(_users.Keys, k => _server.SendMove(k, move));
            }
        }

        public void PlayerExit(int identity)
        {
            if (_users.ContainsKey(identity))
            {
                Parallel.ForEach(_users.Where(t => t.Key != identity), t => _server.SendExitMessage(t.Key, t.Value.identity));

                if (identity == observeHost)
                {
                    var observers = GetObserverList();
                    Parallel.ForEach(observers, ob => _server.SendExitMessage(ob, _users[identity].identity));
                }
                BattleEnd = true;
            }
        }

        public void ReceiveTieMessage(int identity, TieMessage message)
        {
            if (!_users.ContainsKey(identity)) return;
            if (message == TieMessage.TieRequest)
            {
                if (_tieing)
                {
                    _server.SendTieMessage(identity, "", TieMessage.Fail);
                    return;
                }
                else
                {
                    _tieing = true;
                }
            }
            if (!_tieing) return;
            if (message == TieMessage.AgreeTie)
            {
                _agreeTieCounter++;
                if (_agreeTieCounter + 1 != _users.Count) return;
            }
            else if (message == TieMessage.RefuseTie)
            {
                _tieing = false;
                _agreeTieCounter = 0;
            }
            foreach (int userKey in _users.Keys)
            {
                if (message != TieMessage.AgreeTie && userKey == identity) continue;
                _server.SendTieMessage(userKey, _users[identity].identity, message);
            }
        }

        public void PlayerTimeUp(int identity, string player)
        {
            if (_users.ContainsKey(identity))
            {
                Parallel.ForEach(_users.Keys, k => _server.PlayerTimeUp(k, player));
            }
        }

        public void RegistObserver(int identity)
        {
            _observers.Add(identity);
            if (_battleInfo != null) _server.SendBattleInfo(identity, _battleInfo);
        }

        public void ReceiveBattleInfo(BattleInfo info)
        {
            _battleInfo = info;
            var observers = GetObserverList();
            Parallel.ForEach(observers, ob => _server.SendBattleInfo(ob, info));
        }

        public void ReceiveBattleSnapshot(BattleSnapshot snapshot)
        {
            var observers = GetObserverList();
            Parallel.ForEach(observers, ob => _server.SendBattleSnapshot(ob, snapshot));
        }

        private IEnumerable<int> GetObserverList()
        {
            return _observers.ToArray();
        }

        public bool BattleEnd { private set; get; }
    }
}
